﻿/************************************************************************************
 * xujiajun@yoozoo.com 御剑
 * 继承了层级抽象类的用于离散分布对象的层级
 ***********************************************************************************/

using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using Yoozoo;
using Yoozoo.Tools.Debugger;

namespace WorldMapBase.Layer
{
    /// <summary>
    /// 继承了层级抽象类的用于离散分布对象的层级
    /// 将整个场景按照栅格进行划分，每个栅格内保存着坐落于该栅格内的对象集合。（使用空间换时间）
    /// 当要检测对象是否坐落于视野内时，只用检测视野覆盖的栅格内的对象
    /// </summary>
    public class DiscreteObjectLayer : Layer
    {
        public bool debug = false;
        
        /// <summary>
        /// 内部辅助类，用来表示离散分布的对象
        /// </summary>
        private class DiscreteObject
        {
            /// <summary>
            /// 是否隐藏标记
            /// </summary>
            public bool hideFlag;

            /// <summary>
            /// 识别ID
            /// </summary>
            public string id;

            /// <summary>
            /// 横坐标
            /// </summary>
            public float x;

            /// <summary>
            /// 纵坐标
            /// </summary>
            public float y;
        }

        /// <summary>
        /// 格子坐标所在格子映射对象的集合
        /// </summary>
        private Dictionary<int, Dictionary<int, HashSet<string>>> m_XYObjectSets;

        /// <summary>
        /// 已初始化的离散分布对象和ID的映射字典
        /// </summary>
        private Dictionary<string, DiscreteObject> m_ObjectDic;

        /// <summary>
        /// 用于回收离散分布对象的池子
        /// </summary>
        private Stack<DiscreteObject> m_ObjectStack;

        /// <summary>
        /// 已添加并待通知事件的对象集合
        /// </summary>
        private HashSet<DiscreteObject> m_AddedObjectSets;
        
        /// <summary>
        /// 已更新位置并待通知事件的对象集合
        /// </summary>
        private HashSet<DiscreteObject> m_UpdatedObjectSets;
        
        /// <summary>
        /// 已移除并待通知事件的对象集合
        /// </summary>
        private HashSet<DiscreteObject> m_RemovedObjectSets;
        
        /// <summary>
        /// 离散分布对象层级的监听者
        /// </summary>
        private IDiscreteObjectLayerListener m_Listener;
        
        /// <summary>
        /// 格子宽度（米）
        /// </summary>
        private int m_GridWidth;

        /// <summary>
        /// 格子高度（米）
        /// </summary>
        private int m_GridHeight;

        /// <summary>
        /// 上一帧视野覆盖的最左边格子的横坐标
        /// </summary>
        private int m_LastLeftGridX;

        /// <summary>
        /// 上一帧视野覆盖的最右边格子的横坐标
        /// </summary>
        private int m_LastRightGridX;

        /// <summary>
        /// 上一帧视野覆盖的最下边格子的纵坐标
        /// </summary>
        private int m_LastBottomGridY;

        /// <summary>
        /// 上一帧视野覆盖的最上边格子的纵坐标
        /// </summary>
        private int m_LastTopGridY;

        /// <summary>
        /// 是否第一次运行的标记
        /// </summary>
        private bool b_FirstRuntimeFlag;

        private static int INDEX = 0;
        private int m_LayerName;
        
        /// <summary>
        /// 构造层级
        /// </summary>
        /// <param name="listener">离散分布对象层级的监听者</param>
        /// <param name="gridSize">格子尺寸（米）</param>
        public DiscreteObjectLayer(IDiscreteObjectLayerListener listener, int gridSize) : this(listener, gridSize, gridSize)
        {

        }

        /// <summary>
        /// 构造层级
        /// </summary>
        /// <param name="listener">离散分布对象层级的监听者</param>
        /// <param name="gridWidth">格子宽度（米）</param>
        /// <param name="gridHeight">格子高度（米）</param>
        public DiscreteObjectLayer(IDiscreteObjectLayerListener listener, int gridWidth, int gridHeight)
        {
            m_LayerName = INDEX++;
            m_ObjectDic = new Dictionary<string, DiscreteObject>();
            m_ObjectStack = new Stack<DiscreteObject>();
            m_XYObjectSets = new Dictionary<int, Dictionary<int, HashSet<string>>>();
            // m_2DestroyObjectQueue = new Queue<DiscreteObject>();
            // m_FirstRuntimeObjectDic = new Dictionary<string, DiscreteObject>();
            m_AddedObjectSets = new HashSet<DiscreteObject>();
            m_UpdatedObjectSets = new HashSet<DiscreteObject>();
            m_RemovedObjectSets = new HashSet<DiscreteObject>();
            m_GridWidth = gridWidth;
            m_GridHeight = gridHeight;
            b_FirstRuntimeFlag = true;
            SetListener(listener);
        }

        /// <summary>
        /// 构造层级
        /// </summary>
        /// <param name="gridSize">格子尺寸（米）</param>
        public DiscreteObjectLayer(int gridSize) : this(null, gridSize, gridSize)
        {

        }

        /// <summary>
        /// 构造层级
        /// </summary>
        /// <param name="gridWidth">格子宽度（米）</param>
        /// <param name="gridHeight">格子高度（米）</param>
        public DiscreteObjectLayer(int gridWidth, int gridHeight) : this(null, gridWidth, gridHeight)
        {

        }

        /// <summary>
        /// 设置离散分布对象层级的监听者
        /// </summary>
        /// <param name="listener">监听者</param>
        public void SetListener(IDiscreteObjectLayerListener listener)
        {
            m_Listener = listener;
        }

        /// <summary>
        /// 添加指定ID的对象
        /// </summary>
        /// <param name="id">对象的识别ID</param>
        /// <param name="x">对象的横坐标</param>
        /// <param name="y">对象的纵坐标</param>
        /// <returns>是否添加成功，如果已经存在ID则返回失败，否则返回成功</returns>
        public bool AddObject(string id, float x, float y)
        {
            if (m_ObjectDic.ContainsKey(id))
            {
                return false;
            }

            DiscreteObject obj;
            if (m_ObjectStack.Count > 0)
            {
                obj = m_ObjectStack.Pop();
            }
            else
            {
                obj = new DiscreteObject();
            }
            obj.id = id;
            obj.x = x;
            obj.y = y;
            obj.hideFlag = true;

            int gridX;
            int gridY;
            InitializeGridXY(x, y, out gridX, out gridY);
            m_XYObjectSets[gridX][gridY].Add(id);

            m_ObjectDic.Add(id, obj);

            m_AddedObjectSets.Add(obj);

            if (debug)
            {
                Debug.LogWarning($"addObject m_LayerName:{m_LayerName} id:{id} x:{x} y:{y} gridX:{gridX} gridY:{gridY}");
            }
            return true;
        }

        /// <summary>
        /// 更新指定ID对象的坐标
        /// </summary>
        /// <param name="id"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns>是否更新成功，如果找到了指定ID的对象则返回成功，否则返回失败</returns>
        public bool UpdateObject(string id, float x, float y)
        {
            DiscreteObject obj;
            // 如果存在于已初始化的容器内，更新坐标并判断是否在视野内
            if (m_ObjectDic.TryGetValue(id, out obj))
            {
                int prevGridX;
                int prevGridY;
                InitializeGridXY(obj.x, obj.y, out prevGridX, out prevGridY);

                int gridX;
                int gridY;
                InitializeGridXY(x, y, out gridX, out gridY);

                obj.x = x;
                obj.y = y;

                if (prevGridX != gridX || prevGridY != gridY)
                {
                    m_XYObjectSets[prevGridX][prevGridY].Remove(id);
                    m_XYObjectSets[gridX][gridY].Add(id);
                }

                if (!m_AddedObjectSets.Contains(obj) && !m_UpdatedObjectSets.Contains(obj))
                {
                    m_UpdatedObjectSets.Add(obj);
                }
                
                if (debug)
                {
                    Debug.LogWarning($"UpdateObject m_LayerName:{m_LayerName} id:{id} x:{x} y:{y} gridX:{gridX} gridY:{gridY}");
                }
                // if (!b_FirstRuntimeFlag)
                // {
                //     //var hideFlag = IsHide(obj, m_LastlbViewPos, m_LastrtViewPos);
                //     var hideFlag = IsGridHide(gridX, gridY, m_LastLeftGridX, m_LastBottomGridY, m_LastRightGridX, m_LastTopGridY);
                //     if (obj.hideFlag != hideFlag)
                //     {
                //         m_Listener.OnObjectVisibleChange(id, hideFlag);
                //         obj.hideFlag = hideFlag;
                //         Debug.LogWarning($"UpdateObject id:{id} x:{x} y:{y} gridX:{gridX} gridY:{gridY}");
                //     }
                // }
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 移除指定ID的对象
        /// </summary>
        /// <param name="id">对象的识别ID</param>
        /// <returns>是否移除成功，如果已经存在ID则返回成功，否则返回失败</returns>
        public bool RemoveObject(string id)
        {
            DiscreteObject obj;
            if (m_ObjectDic.TryGetValue(id, out obj))
            {
                int gridX;
                int gridY;
                InitializeGridXY(obj.x, obj.y, out gridX, out gridY);
                m_XYObjectSets[gridX][gridY].Remove(obj.id);

                //m_ObjectStack.Push(obj);
                m_ObjectDic.Remove(id);

                if (!m_AddedObjectSets.Remove(obj))
                {
                    m_RemovedObjectSets.Add(obj);
                    m_UpdatedObjectSets.Remove(obj);
                }
                
                if (debug)
                {
                    Debug.LogWarning($"RemoveObject m_LayerName:{m_LayerName} id:{id} gridX:{gridX} gridY:{gridY}");
                }

                return true;
            }
            else
            {
                return false;
            }
        }

        protected override void OnCreate()
        {

        }

        protected override void OnDestroy()
        {
            foreach (var obj in m_ObjectDic.Values)
            {
                if (!obj.hideFlag)
                {
                    m_Listener.OnObjectVisibleChange(obj.id, true);
                }
            }

            m_ObjectDic.Clear();
            m_ObjectStack.Clear();
            m_XYObjectSets.Clear();
            m_AddedObjectSets.Clear();
            m_UpdatedObjectSets.Clear();
            m_RemovedObjectSets.Clear();
            b_FirstRuntimeFlag = true;
        }

        protected override void OnUpdateViewRect(int4 viewRect)
        {
            //Debug.LogError(viewRect);
            
            var lbViewPos = viewRect.xy;
            var rtViewPos = lbViewPos + viewRect.zw;

            int leftGridX;
            int rightGridX;
            int bottomGridY;
            int topGridY;
            InitializeGridXY(lbViewPos.x, lbViewPos.y, out leftGridX, out bottomGridY);
            InitializeGridXY(rtViewPos.x, rtViewPos.y, out rightGridX, out topGridY);

            if (debug)
            {
                Debug.LogWarning($"m_LayerName:{m_LayerName} viewRect:{viewRect.xyzw}");
                Debug.LogWarning($"m_LayerName:{m_LayerName} leftGridX:{leftGridX} rightGridX:{rightGridX} bottomGridY:{bottomGridY} topGridY:{topGridY}");
            }

            // 通知刚移除的对象的显示状况
            if (m_RemovedObjectSets.Count > 0)
            {
                foreach (var obj in m_RemovedObjectSets)
                {
                    m_Listener.OnObjectVisibleChange(obj.id, true);
                    obj.hideFlag = true;
                    m_ObjectStack.Push(obj);
                }
                m_RemovedObjectSets.Clear();
            }
            
            // 通知刚添加的对象的显示状况
            if (m_AddedObjectSets.Count > 0)
            {
                foreach (var obj in m_AddedObjectSets)
                {
                    int gridX;
                    int gridY;
                    InitializeGridXY(obj.x, obj.y, out gridX, out gridY);
                    var hideFlag = IsGridHide(gridX, gridY, leftGridX, bottomGridY, rightGridX, topGridY);
                    m_Listener.OnObjectVisibleChange(obj.id, hideFlag);
                    obj.hideFlag = hideFlag;
                }
                m_AddedObjectSets.Clear();
            }
            
            // 通知刚更新位置的对象的显示状况
            if (m_UpdatedObjectSets.Count > 0)
            {
                foreach (var obj in m_UpdatedObjectSets)
                {
                    int gridX;
                    int gridY;
                    InitializeGridXY(obj.x, obj.y, out gridX, out gridY);
                    var hideFlag = IsGridHide(gridX, gridY, leftGridX, bottomGridY, rightGridX, topGridY);
                    m_Listener.OnObjectVisibleChange(obj.id, hideFlag);
                    obj.hideFlag = hideFlag;
                }
                m_UpdatedObjectSets.Clear();
            }
            
            if (!b_FirstRuntimeFlag)
            {
                // 如果格子坐标发生变动，只计算有删除和添加的部分
                if (m_LastLeftGridX != leftGridX || m_LastRightGridX != rightGridX || m_LastTopGridY != topGridY || m_LastBottomGridY != bottomGridY)
                {
                    if (debug)
                    {
                        Debug.LogWarning($"m_LayerName:{m_LayerName} m_ObjectDic.Count:{m_ObjectDic.Count}");
                        Debug.LogWarning($"Grid Change leftGridX:{leftGridX} rightGridX:{rightGridX} topGridY:{topGridY} bottomGridY:{bottomGridY}");
                        Debug.LogWarning($"m_LastLeftGridX:{m_LastLeftGridX} m_LastRightGridX:{m_LastRightGridX} m_LastTopGridY:{m_LastTopGridY} m_LastBottomGridY:{m_LastBottomGridY}");
                    }

                    if (m_LastLeftGridX < leftGridX)
                    {
                        // 隐藏左边
                        NotifyObjectsHide(m_LastLeftGridX, m_LastBottomGridY, Mathf.Min(m_LastRightGridX, leftGridX - 1), m_LastTopGridY);
                    }
                    if (m_LastRightGridX > rightGridX)
                    {
                        // 隐藏右边
                        NotifyObjectsHide(Mathf.Max(m_LastLeftGridX, rightGridX + 1), m_LastBottomGridY, m_LastRightGridX, m_LastTopGridY);
                    }
                    if (m_LastTopGridY > topGridY)
                    {
                        // 隐藏上边
                        NotifyObjectsHide(Mathf.Max(m_LastLeftGridX, leftGridX), Mathf.Max(topGridY + 1, m_LastBottomGridY), Mathf.Min(m_LastRightGridX, rightGridX), m_LastTopGridY);
                    }
                    if (m_LastBottomGridY < bottomGridY)
                    {
                        // 隐藏下边
                        NotifyObjectsHide(Mathf.Max(m_LastLeftGridX, leftGridX), m_LastBottomGridY, Mathf.Min(m_LastRightGridX, rightGridX), Mathf.Min(m_LastTopGridY, bottomGridY - 1));
                    }

                    if (m_LastLeftGridX > leftGridX)
                    {
                        // 显示左边
                        NotifyObjectsShow(leftGridX, bottomGridY, Mathf.Min(m_LastLeftGridX - 1, rightGridX), topGridY);
                    }
                    if (m_LastRightGridX < rightGridX)
                    {
                        // 显示右边
                        NotifyObjectsShow(Mathf.Max(m_LastRightGridX + 1, leftGridX), bottomGridY, rightGridX, topGridY);
                    }
                    if (m_LastTopGridY < topGridY)
                    {
                        // 显示上边
                        //NotifyObjectsShow(Mathf.Max(m_LastLeftGridX, leftGridX + 1), Mathf.Max(m_LastTopGridY, bottomGridY + 1), Mathf.Min(m_LastRightGridX, rightGridX - 1), topGridY - 1);
                        NotifyObjectsShow(Mathf.Max(m_LastLeftGridX, leftGridX), Mathf.Max(m_LastTopGridY + 1, bottomGridY), Mathf.Min(m_LastRightGridX, rightGridX), topGridY);
                    }
                    if (m_LastBottomGridY > bottomGridY)
                    {
                        // 显示下边
                        //NotifyObjectsShow(Mathf.Max(m_LastLeftGridX, leftGridX), bottomGridY + 1, Mathf.Min(m_LastRightGridX, rightGridX), Mathf.Min(m_LastBottomGridY, topGridY - 1));
                        NotifyObjectsShow(Mathf.Max(m_LastLeftGridX, leftGridX), bottomGridY, Mathf.Min(m_LastRightGridX, rightGridX), Mathf.Min(m_LastBottomGridY - 1, topGridY));
                    }
                }

                //// 边缘的格子内的对象进行更精确的碰撞检测再通知结果
                //CheckObjectsVisibleInViewRect(leftGridX, topGridY, rightGridX, topGridY, lbViewPos, rtViewPos);
                //CheckObjectsVisibleInViewRect(leftGridX, bottomGridY, rightGridX, bottomGridY, lbViewPos, rtViewPos);
                //CheckObjectsVisibleInViewRect(leftGridX, bottomGridY + 1, leftGridX, topGridY - 1, lbViewPos, rtViewPos);
                //CheckObjectsVisibleInViewRect(rightGridX, bottomGridY + 1, rightGridX, topGridY - 1, lbViewPos, rtViewPos);
            }

            b_FirstRuntimeFlag = false;
            m_LastLeftGridX = leftGridX;
            m_LastRightGridX = rightGridX;
            m_LastBottomGridY = bottomGridY;
            m_LastTopGridY = topGridY;

        }

        /// <summary>
        /// 将世界坐标转为格子坐标
        /// </summary>
        /// <param name="worldX">世界横坐标</param>
        /// <param name="worldY">世界纵坐标</param>
        /// <param name="x">格子横坐标</param>
        /// <param name="y">格子纵坐标</param>
        private void InitializeGridXY(float worldX, float worldY, out int x, out int y)
        {
            x = (int)(worldX / m_GridWidth);
            y = (int)(worldY / m_GridHeight);
            if (!m_XYObjectSets.ContainsKey(x))
            {
                m_XYObjectSets.Add(x, new Dictionary<int, HashSet<string>>());
            }
            if (!m_XYObjectSets[x].ContainsKey(y))
            {
                m_XYObjectSets[x].Add(y, new HashSet<string>());
            }
        }

        /// <summary>
        /// 检测对象是否在视野内
        /// </summary>
        /// <param name="obj">对象</param>
        /// <param name="lbViewPos">视野左下角</param>
        /// <param name="rtViewPos">视野右上角</param>
        /// <returns>是否隐藏的结果。true表示隐藏，false表示显示</returns>
        private bool IsHide(DiscreteObject obj, int2 lbViewPos, int2 rtViewPos)
        {
            return !(obj.x >= lbViewPos.x && obj.x <= rtViewPos.x && obj.y >= lbViewPos.y && obj.y <= rtViewPos.y);
        }

        /// <summary>
        /// 该格子是否处于是视野内
        /// </summary>
        /// <param name="gridX"></param>
        /// <param name="gridY"></param>
        /// <param name="startGridX"></param>
        /// <param name="startGridY"></param>
        /// <param name="endGridX"></param>
        /// <param name="endGridY"></param>
        /// <returns>是否隐藏的结果。true表示隐藏，false表示显示</returns>
        private bool IsGridHide(int gridX, int gridY, int startGridX, int startGridY, int endGridX, int endGridY)
        {
            return !(gridX >= startGridX && gridX <= endGridX && gridY >= startGridY && gridY <= endGridY);
        }

        // /// <summary>
        // /// 检测指定区间内的对象是否在视野内
        // /// </summary>
        // /// <param name="startGridX">起始格子横坐标</param>
        // /// <param name="startGridY">起始格子纵坐标</param>
        // /// <param name="endGridX">结束格子横坐标</param>
        // /// <param name="endGridY">结束格子纵坐标</param>
        // /// <param name="lbViewPos">视野左下角</param>
        // /// <param name="rtViewPos">视野右上角</param>
        // private void CheckObjectsVisibleInViewRect(int startGridX, int startGridY, int endGridX, int endGridY, int2 lbViewPos, int2 rtViewPos)
        // {
        //     for (int j = startGridY; j <= endGridY; j++)
        //     {
        //         for (int i = startGridX; i <= endGridX; i++)
        //         {
        //             Dictionary<int, HashSet<string>> xyDic;
        //             if (m_XYObjectSets.TryGetValue(i, out xyDic))
        //             {
        //                 HashSet<string> idSets;
        //                 if (xyDic.TryGetValue(j, out idSets))
        //                 {
        //                     foreach (var id in idSets)
        //                     {
        //                         var obj = m_ObjectDic[id];
        //                         var newHideFlag = IsHide(obj, lbViewPos, rtViewPos);
        //
        //                         if (obj.hideFlag != newHideFlag)
        //                         {
        //                             m_Listener.OnObjectVisibleChange(obj.id, newHideFlag);
        //                             obj.hideFlag = newHideFlag;
        //                         }
        //                     }
        //                 }
        //             }
        //         }
        //     }
        // }

        /// <summary>
        /// 通知指定区域内的对象显示
        /// </summary>
        /// <param name="startGridX">起始格子横坐标</param>
        /// <param name="startGridY">起始格子纵坐标</param>
        /// <param name="endGridX">结束格子横坐标</param>
        /// <param name="endGridY">结束格子纵坐标</param>
        private void NotifyObjectsShow(int startGridX, int startGridY, int endGridX, int endGridY)
        {
            for (int j = startGridY; j <= endGridY; j++)
            {
                for (int i = startGridX; i <= endGridX; i++)
                {
                    Dictionary<int, HashSet<string>> xyDic;
                    if (m_XYObjectSets.TryGetValue(i, out xyDic))
                    {
                        HashSet<string> idSets;
                        if (xyDic.TryGetValue(j, out idSets))
                        {
                            foreach (var id in idSets)
                            {
                                var obj = m_ObjectDic[id];
                                
                                if (obj.hideFlag)
                                {
                                    m_Listener.OnObjectVisibleChange(obj.id, false);
                                    obj.hideFlag = false;
                                    if (Yoozoo.Gameplay.GTAGameStart.IsDebug)
                                    {
                                        Debug.LogWarning($"NotifyObjectsShow id:{id}");
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 通知指定区域内的对象隐藏
        /// </summary>
        /// <param name="startGridX">起始格子横坐标</param>
        /// <param name="startGridY">起始格子纵坐标</param>
        /// <param name="endGridX">结束格子横坐标</param>
        /// <param name="endGridY">结束格子纵坐标</param>
        private void NotifyObjectsHide(int startGridX, int startGridY, int endGridX, int endGridY)
        {
            for (int j = startGridY; j <= endGridY; j++)
            {
                for (int i = startGridX; i <= endGridX; i++)
                {
                    Dictionary<int, HashSet<string>> xyDic;
                    if (m_XYObjectSets.TryGetValue(i, out xyDic))
                    {
                        HashSet<string> idSets;
                        if (xyDic.TryGetValue(j, out idSets))
                        {
                            foreach (var id in idSets)
                            {
                                var obj = m_ObjectDic[id];

                                if (!obj.hideFlag)
                                {
                                    m_Listener.OnObjectVisibleChange(obj.id, true);
                                    obj.hideFlag = true;
                                    if (Yoozoo.Gameplay.GTAGameStart.IsDebug)
                                    {
                                        Debug.LogWarning($"NotifyObjectsHide id:{id}");
                                    }
                                   
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
